/**********************************************************************************
 *  jcw - this is a environment for solving japan crosswords by users on computer *
 *  Copyright (C) 2008 by pluton <plutonpluton@mail.ru>                           *
 *                                                                                *
 *  This program is free software; you can redistribute it and/or modify          *
 *  it under the terms of the GNU General Public License as published by          *
 *  the Free Software Foundation; either version 2 of the License, or             *
 *  (at your option) any later version.                                           *
 *                                                                                *
 *  This program is distributed in the hope that it will be useful,               *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of                *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
 *  GNU General Public License for more details.                                  *
 *                                                                                *
 *  You should have received a copy of the GNU General Public License along       *
 *  with this program; if not, write to the Free Software Foundation, Inc.,       *
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.                   *
 *********************************************************************************/

#include "classiccw.h"

//macro, which sets error flag
#define setDeleting \
	{ \
		needDelete = true; \
		return; \
	}

//check ok flag
#define checkOk \
	if (!ok) \
		setDeleting;

ClassicCW::ClassicCW(QTime* time, QWidget* parent, Qt::WindowFlags f)
	: RectCrossword(CWType::Classic, time, parent, f)
{
	mode = CWType::SolvingCrossword;
}

void ClassicCW::init(const QDomElement& root)
{
	QDomElement domElem = root.toElement();
	name = domElem.attribute("name");
	author = domElem.attribute("author");
	date = domElem.attribute("date");
	bool ok;
	QString ver = domElem.attribute("ver");
	numCorrections = domElem.attribute("corr", "0").toUInt(&ok);
	checkOk;
	if (this->time != NULL)
	{
		quint32 solvSec = domElem.attribute("time", "0").toUInt(&ok);
		checkOk;
		this->time->setHMS(solvSec / 3600, solvSec / 60 % 60, solvSec % 60);
	}

	quint16 i;
	quint16 j;
	QStringList list;
	QStringList list2;
	QDomNodeList nodes, nodes2;

	domElem = root.toElement();

	/* let's find and read top header */
	nodes = domElem.elementsByTagName("topheader");
	if (ver == "0.2")
	{
		if (!nodes.isEmpty())
			domElem = nodes.item(0).toElement();
		else
			setDeleting;

		nodes = domElem.elementsByTagName("values");
	}
	if (!nodes.isEmpty())
	{
		domElem = nodes.item(0).toElement();
		if (!domElem.isNull())
		{
			th = 0;
			list = domElem.text().split(';', QString::SkipEmptyParts);
			fw = list.size();
			thdr = new qint16* [fw];
			thcnt = new quint16 [fw];
			bzero(thdr, fw * sizeof(qint16*));
			for (i = 0; i < fw; i++)
			{
				list2 = list.at(i).split(',', QString::SkipEmptyParts);
				thdr[i] = new qint16[list2.size()];
				bzero(thdr[i], list2.size() * sizeof(qint16));
				thcnt[i] = list2.size();
				if (th < list2.size())
					th = list2.size();
				for (j = 0; j < list2.size(); j++)
				{
					thdr[i][j] = list2.at(j).toInt(&ok);
					checkOk;
				}
			}
		}
	}
	else
		setDeleting;
	colsRes = new QBitArray(fw);

	domElem = root.toElement();

	/* let's find and read top header */
	nodes = domElem.elementsByTagName("leftheader");
	if (ver == "0.2")
	{
		if (!nodes.isEmpty())
			domElem = nodes.item(0).toElement();
		else
			setDeleting;

		nodes = domElem.elementsByTagName("values");
	}

	if (!nodes.isEmpty())
	{
		domElem = nodes.item(0).toElement();
		if (!domElem.isNull())
		{
			lw = 0;
			list = domElem.text().split(';', QString::SkipEmptyParts);
			fh = list.size();
			lhdr = new qint16* [fh];
			lhcnt = new quint16 [fh];
			bzero(lhdr, fh * sizeof(qint16*));
			for (i = 0; i < fh; i++)
			{
				list2 = list.at(i).split(',', QString::SkipEmptyParts);
				lhdr[i] = new qint16[list2.size()];
				bzero(lhdr[i], list2.size() * sizeof(qint16));
				lhcnt[i] = list2.size();
				if (lw < list2.size())
					lw = list2.size();
				for (j = 0; j < list2.size(); j++)
				{
					lhdr[i][j] = list2.at(j).toInt(&ok);
					checkOk;
				}
			}
		}
	}
	else
		setDeleting;

	rowsRes = new QBitArray(fh);

	field = new CellState* [fw]; //создание двумерного динамического массива

	bzero(field, fw * sizeof(CellState*));
	for (i = 0; i < fw; i++)
	{
		field[i] = new CellState[fh];
		bzero(field[i], fh * sizeof(CellState));
	}

	/* ищем и читаем состояния клеток поля */
	if (!nodes.isEmpty())
		domElem = nodes.item(0).toElement();
		if (!domElem.isNull())
		{
			nodes = domElem.elementsByTagName("cell");
			for (i = 0; i < nodes.length(); i++)
			{
				quint16 x = nodes.item(i).toElement().attribute("col").toUInt(&ok);
				checkOk;
				quint16 y = nodes.item(i).toElement().attribute("row").toUInt(&ok);
				checkOk;
				QString state = nodes.item(i).toElement().attribute("state");
				if (state == "filled")
					field[x][y] = csFilled;
				else if (state  == "empty")
					field[x][y] = csEmpty;
			}
		}

	nodes = root.elementsByTagName("comment");
	if (!nodes.isEmpty())
	{
		domElem = nodes.item(0).toElement();
		if (!domElem.isNull())
			comment = domElem.text();
	}

	keyFillPressed = false;
	keyEmptyPressed = false;
	keyUndefPressed = false;
	fX = oldFX = lw;
	fY = oldFY = th;
	undoList.clear();
	redoList.clear();
	setMouseTracking(true);				// включаем отслеживание движения мыши
	setFocusPolicy(Qt::StrongFocus);
}

ClassicCW::ClassicCW(const quint16 width, const quint16 height, const quint16 topHeaderHeight, const quint16 leftHeaderWidth, QTime* time, QWidget* parent, Qt::WindowFlags f)
	: RectCrossword(CWType::Classic, time, parent, f)
{
	newCrossword(CWType::FillUpHeaders, width, height, topHeaderHeight, leftHeaderWidth);
}

ClassicCW::ClassicCW(const quint16 width, const quint16 height, QTime* time, QWidget* parent, Qt::WindowFlags f)
	: RectCrossword(CWType::Classic, time, parent, f)
{
	newCrossword(CWType::PaintCrossword, width, height, 0, 0);
}

void ClassicCW::newCrossword(const CWType::CrosswordMode m, const quint16 fieldWidth, const quint16 fieldHeight, const quint16 topHeaderHeight, const quint16 leftHeaderWidth)
{
	quint16 i, j;

	mode = m;
	fw = fieldWidth;
	fh = fieldHeight;
	th = topHeaderHeight;
	lw = leftHeaderWidth;
	name = "";
	author = "";
	date = "";
	numCorrections = 0;
	this->time->setHMS(0, 0, 0); //?
	thdr = new qint16* [fw];
	lhdr = new qint16* [fh];
	thcnt = new quint16 [fw];
	lhcnt = new quint16 [fh];

	bzero(thdr, fw * sizeof(qint16*));
	bzero(lhdr, fh * sizeof(qint16*));

	for (i = 0; i < fw; i++)
	{
		thdr[i] = new qint16[th];
		thcnt[i] = th;
		for (j = 0; j < th; j++)
			thdr[i][j] = 0;
	}
	for (i = 0; i < fh; i++)
	{
		lhdr[i] = new qint16[lw];
		lhcnt[i] = lw;
		for (j = 0; j < lw; j++)
			lhdr[i][j] = 0;
	}

	rowsRes = new QBitArray(fh); //?

	field = new CellState* [fw]; //создание двумерного динамического массива
	bzero(field, fw * sizeof(CellState*));
	for (i = 0; i < fw; i++)
	{
		field[i] = new CellState[fh];
		bzero(field[i], fh * sizeof(CellState));
	}

	comment = "";

	keyFillPressed = false; //?
	keyEmptyPressed = false; //?
	keyUndefPressed = false; //?
	fX = oldFX = lw; //?
	fY = oldFY = th; //?
	editHeaderX = lw;
	editHeaderY = 0;
	editHeaderValue = 0;
	isEdit = false;
	undoList.clear();
	redoList.clear();
	setMouseTracking(true);	// включаем отслеживание движения мыши
	setFocusPolicy(Qt::StrongFocus); //?
}

ClassicCW::~ClassicCW()
{
	// ^^^ надо бы удалить всё, что мы тама создали
	quint16 i;
	if (rowsRes != NULL)
		delete rowsRes;
	if (lhdr != NULL)
	{
		for (i = 0; i < lw; i++)
			if (lhdr[i] != NULL)
				delete[] lhdr[i];
		delete[] lhdr;
	}
	if (colsRes != NULL)
		delete colsRes;
	if (thdr != NULL)
	{
		for (i = 0; i < fw; i++)
			if (thdr[i] != NULL)
				delete[] thdr[i];
		delete[] thdr;
	}
	if (field != NULL)
	{
		for (i = 0; i < fw; i++)
			if (field[i] != NULL)
				delete[] field[i];
		delete[] field;
	}
	undoList.clear();
	redoList.clear();
}

void ClassicCW::paintEvent(QPaintEvent* /*event*/)
{
	QStylePainter painter(this);	//object for painting
	painter.drawPixmap(0, 0, pixmap);
	
	if (hasFocus())
	{
		QStyleOptionFocusRect option;
		option.initFrom(this);
		//option.backgroundColor = palette().light().color();
		painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
	}
}

bool ClassicCW::calcHighlightXY(const QPoint& originPoint)
{
	bool changed = false;
	fX = (originPoint.x() - margin) / cellSize;					// координата клетки
	fY = (originPoint.y() - margin) / cellSize;					// на поле
	if ((fX < lw && fY < th) || (fX >= fw + lw) || (fY >= fh + th))
		//если выделенная клетка не находится в области поля или заголовков, восстанавливаем предыдущее значение
	{
		fX = oldFX;
		fY = oldFY;
	}
	else if (fX != oldFX || fY != oldFY)
	{
		changed = true;
		updatePixmap();
		oldFX = fX;
		oldFY = fY;
	}
	return changed;
}

void ClassicCW::mouseMoveEvent(QMouseEvent* event)
{
	if (calcHighlightXY(event->pos()))
	{
		Qt::MouseButton button = Qt::NoButton;
		if (event->buttons().testFlag(Qt::LeftButton))			// если двигаем мышу, и
			button =  Qt::LeftButton;
		else if (event->buttons().testFlag(Qt::RightButton))	// нажата одна из кнопок
			button =  Qt::RightButton;
		else if (event->buttons().testFlag(Qt::MidButton))		// то генерим событие
			button =  Qt::MidButton;
		if (button != Qt::NoButton)								// нажатия кнопки
		{
			QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(),
					button, event->buttons(), event->modifiers());
			mousePressEvent(mouseEvent);
		}
	}
	event->accept();
}

void ClassicCW::mousePressEvent(QMouseEvent* event)
{
	calcHighlightXY(event->pos());
	CellState newState;
	switch (event->button())
	{
		case Qt::LeftButton:
			newState = csFilled;
			if (mode == CWType::FillUpHeaders && (editHeaderX != fX || editHeaderY != fY))
			{
				editHeaderX = 0;
				editHeaderY = 0;
				editHeaderValue = 0;
				isEdit = false;

				if ((fX >= lw && fY < th) || (fX < lw && fY >= th))
				{
					editHeaderX = fX;
					editHeaderY = fY;
				}
			}
			break;
		case Qt::MidButton:
			newState = csUndef;
			break;
		case Qt::RightButton:
			newState = csEmpty;
			break;
	}
	if (mode != CWType::FillUpHeaders)
		changeCellState(newState);
	event->accept();
	updatePixmap();
}

void ClassicCW::wheelEvent(QWheelEvent* event)
{
	if (event->modifiers().testFlag(Qt::ControlModifier))
	{									// если нажат Ctrl, то изменяем размер клетки
		quint16 d = qAbs(event->delta() / 120);
		if (event->delta() > 0)			// скроллинг вверх
			cellSize += d;
		else
			if (cellSize + d > MIN_CELLSIZE)
				cellSize -= d;
	}
	else if (event->modifiers().testFlag(Qt::ShiftModifier))
	{									// если нажат Shift, то включаем горизонтальный скроллинг
		QWheelEvent* ev = new QWheelEvent(event->pos(), event->delta(), event->buttons(),
				event->modifiers(), Qt::Horizontal);
		qApp->postEvent(parentWidget(), ev);
	}
	else
	{
		event->setAccepted(false);
		return;
	}
	updatePixmap();
	calcHighlightXY(event->pos());
	event->setAccepted(true);
	return;
}

#define checkPressedKeys \
		if (keyFillPressed) \
			changeCellState(csFilled); \
		else if (keyEmptyPressed) \
			changeCellState(csEmpty); \
		else if (keyUndefPressed) \
			changeCellState(csUndef);

void ClassicCW::appendValue(QString val)
{
	if (mode == CWType::FillUpHeaders)
		setHeaderValue(QString::number(editHeaderValue).append(val).toUShort());
	isEdit = true;
}

void ClassicCW::setHeaderValue(quint16 val)
{
	editHeaderValue = val;
	if (editHeaderY < th && editHeaderX >= lw)
		thdr[editHeaderX - lw][editHeaderY] = val;
	if (editHeaderX < lw && editHeaderY >= th)
		lhdr[editHeaderY - th][editHeaderX] = val;
	updatePixmap();
}

void ClassicCW::keyPressEvent(QKeyEvent* event)
{
	switch (event->key())
	{
		case Qt::Key_Return:
		case Qt::Key_Enter:
			if (mode == CWType::FillUpHeaders && (editHeaderX > 0 || editHeaderY > 0))
			{
				if (editHeaderY < th)
					if (editHeaderY < th - 1)
						editHeaderY++;
					else if (editHeaderX < (lw + fw - 1))
					{
						editHeaderX++;
						editHeaderY = 0;
					}
					else
					{
						editHeaderX = 0;
						editHeaderY = th;
					}
				else if (editHeaderX < lw)
				{
					if (editHeaderX < lw - 1)
						editHeaderX++;
					else if (editHeaderY < (th + fh - 1))
					{
						editHeaderY++;
						editHeaderX = 0;
					}
					else
					{
						editHeaderX = lw;
						editHeaderY = 0;
					}
				}
				editHeaderValue = 0;
				isEdit = false;
				updatePixmap();
			}
			break;
		case Qt::Key_Up:
			if (mode == CWType::FillUpHeaders)
			{
				if ((editHeaderX >= lw && editHeaderY > 0) || (editHeaderY > th && editHeaderX < lw))
					editHeaderY--;
				editHeaderValue = 0;
				isEdit = false;
			}
			else
				if (fY > th || (fX > lw - 1 && fY > 0))
				{
					fY--;
					checkPressedKeys;
				}
			break;
		case Qt::Key_Down:
			if (mode == CWType::FillUpHeaders)
			{
				if ((editHeaderX >= lw && editHeaderY < th - 1) || (editHeaderY < th + fh - 1 && editHeaderX < lw))
					editHeaderY++;
				editHeaderValue = 0;
				isEdit = false;
			}
			else
				if (fY < fh + th - 1)
				{
					fY++;
					checkPressedKeys;
				}
			break;
		case Qt::Key_Left:
			if (mode == CWType::FillUpHeaders)
			{
				if ((editHeaderY >= th && editHeaderX > 0) || (editHeaderX > lw && editHeaderY < th))
					editHeaderX--;
				editHeaderValue = 0;
				isEdit = false;
			}
			else
				if (fX > lw || (fY > th - 1 && fX > 0))
				{
					fX--;
					checkPressedKeys;
				}
			break;
		case Qt::Key_Right:
			if (mode == CWType::FillUpHeaders)
			{
				if ((editHeaderY >= th && editHeaderX < lw - 1) || (editHeaderX < lw + fw - 1 && editHeaderY < th))
					editHeaderX++;
				editHeaderValue = 0;
				isEdit = false;
			}
			else
				if (fX < fw + lw - 1)
				{
					fX++;
					checkPressedKeys;
				}
			break;
		case Qt::Key_Tab: //works on Ctrl + Tab
			if (mode == CWType::FillUpHeaders)
			{
				if (editHeaderX >= lw && editHeaderY < th)
				{
					editHeaderX = 0;
					editHeaderY = th;
				}
				else if (editHeaderX < lw && editHeaderY >= th)
				{
					editHeaderX = lw;
					editHeaderY = 0;
				}
				editHeaderValue = 0;
				isEdit = false;
			}
			break;
		case Qt::Key_0:
			appendValue("0");
			break;
		case Qt::Key_1:
			appendValue("1");
			break;
		case Qt::Key_2:
			appendValue("2");
			break;
		case Qt::Key_3:
			appendValue("3");
			break;
		case Qt::Key_4:
			appendValue("4");
			break;
		case Qt::Key_5:
			appendValue("5");
			break;
		case Qt::Key_6:
			appendValue("6");
			break;
		case Qt::Key_7:
			appendValue("7");
			break;
		case Qt::Key_8:
			appendValue("8");
			break;
		case Qt::Key_9:
			appendValue("9");
			break;
		case Qt::Key_Delete:
			if (mode == CWType::FillUpHeaders && !isEdit)
			{
				setHeaderValue(0);
				isEdit = false;
			}
			break;
		case Qt::Key_Backspace:
			if (mode == CWType::FillUpHeaders)
			{
				if (QString::number(editHeaderValue).length() > 1)
					setHeaderValue(QString::number(editHeaderValue).left(QString::number(editHeaderValue).length() - 1).toUShort());
				else
					setHeaderValue(0);
				isEdit = true;
			}
			break;
		case Qt::Key_Z:									// Z -> filled
			changeCellState(csFilled);
			keyFillPressed = true;
			break;
		case Qt::Key_X:									// X -> empty
			changeCellState(csEmpty);
			keyEmptyPressed = true;
			break;
		case Qt::Key_C:									// C -> undefined
			changeCellState(csUndef);
			keyUndefPressed = true;
			break;
		case Qt::Key_Plus:								// + -> увеличить размер клетки
			cellSize++;
			break;
		case Qt::Key_Minus:								// - -> уменьшить размер клетки
			if (cellSize > MIN_CELLSIZE)
				cellSize--;
			break;
		default:
			QWidget::keyPressEvent(event);
			return;
	}
	if (mode == CWType::FillUpHeaders)
		emit checkHeaders();
	updatePixmap();
}

void ClassicCW::keyReleaseEvent(QKeyEvent* event)
{
	switch (event->key())
	{
		case Qt::Key_Z:
			keyFillPressed = false;
			break;
		case Qt::Key_X:
			keyEmptyPressed = false;
			break;
		case Qt::Key_C:
			keyUndefPressed = false;
			break;
		default:
			QWidget::keyReleaseEvent(event);
			return;
	}
}

void ClassicCW::changeCellState(CellState newState)
{
	if (isSolved)
		return;
	quint16 fieldX = fX - lw;
	quint16 fieldY = fY - th;

	if (fX >= lw && fX < fw + lw && fY >= th && fY < th + fh)	// если попали на поле
	{
		if (field[fieldX][fieldY] != csUndef && field[fieldX][fieldY] != newState && mode == CWType::SolvingCrossword)
			numCorrections++;

		redoList.clear();
		History step;
		step.coordX = fieldX;
		step.coordY = fieldY;
		step.state = field[fieldX][fieldY];
		undoList.append(step);

		field[fieldX][fieldY] = newState;
		// надо запустить чекалку поля
		emit cellStateChanged(fieldX, fieldY);
	}
	else if (fX < lw && fY >= th && fY < fh + th)	// если попали в левый заголовок
		lhdr[fieldY][fX - (lw - getGroupCnt(false, fieldY))] *= -1;
	else if (fX >= lw && fX < lw + fw && fY < th)	// если попали в верхний заголовок
		thdr[fieldX][fY - (th - getGroupCnt(true, fieldX))] *= -1;
}

void ClassicCW::clearField()
{
	if (isSolved)
		return;
	quint16 i;
	quint16 j;
	for (i = 0; i < fw; i++)
	{
		for (j = 0; j < fh; j++)
			field[i][j] = csUndef;
		for (j = 0; j < th; j++)
			thdr[i][j] = qAbs(thdr[i][j]);
	}
	for (i = 0; i < lw; i++)
		for (j = 0; j < fh; j++)
			lhdr[i][j] = qAbs(lhdr[i][j]);
	numCorrections = 0;
	undoList.clear();
	redoList.clear();
	updatePixmap();
	emit cellStateChanged(quint16(-2), quint16(-2));
}

ClassicCW::CellState** ClassicCW::getField()
{
	return field;
}

void ClassicCW::save()
{
	QDomDocument doc("crossword");
	QDomElement domElem = doc.createElement("crossword");
	
	/* записываем аттрибуты кроссворда: */
	domElem.setAttribute("type", "classic");
	domElem.setAttribute("ver", "0.2");
	if (!name.isEmpty())
		domElem.setAttribute("name", name);
	if (!author.isEmpty())
		domElem.setAttribute("author", author);
	if (!date.isEmpty())
		domElem.setAttribute("date", date);
	if (!comment.isEmpty())
	{
		QDomElement elem = doc.createElement("comment");
		elem.appendChild(doc.createTextNode(comment));
		domElem.appendChild(elem);
	}
	if (mode == CWType::SolvingCrossword)
	{
		domElem.setAttribute("corr", numCorrections);
		if (time != NULL)
		{
			// вычисляем время решения в секундах:
			domElem.setAttribute("time", (time->hour() * 60 * 60) +
					(time->minute() * 60) + time->second());
		}
	}

	QDomElement elem, elem2, elemColour;
	QString text, textColour;

	quint16 i, j, layer;
	elem = doc.createElement("colourmap");

	/* let's save top header */
	elem = doc.createElement("topheader");
	elem2 = doc.createElement("values");
	text.clear();
	for (i = 0; i < fw; i++)
	{
		if (!(text.length() == 0 || text.endsWith(";")))
			text.append(';');
		if (mode != CWType::PaintCrossword)
			for (j = 0; j < ((mode == CWType::FillUpHeaders) ? (th) : (getGroupCnt(true, i))); j++)
			{
				if (thdr[i][j] == 0)
					continue;
				if (!(text.length() == 0 || text.endsWith(";")))
				{
					text.append(',');
				}
				text.append(QString::number(thdr[i][j]));
			}
		else
		{
			quint16 currGroup = 0;
			for (j = 0; j < fh; j++)
			{
				CellState currState = field[i][j];
				if (currState == csFilled && j < fh - 1)
					currGroup++;
				else
				{
					if (j == fh - 1 && currState == csFilled)
						currGroup++;
					if (currGroup != 0)
					{
						if (!(text.length() == 0 || text.endsWith(";")))
							text.append(',');
						text.append(QString::number(currGroup));
						currGroup = 0;
					}
				}
			}
		}
	}
	elem2.appendChild(doc.createTextNode(text));
	elem.appendChild(elem2);
	domElem.appendChild(elem);
	
	/* let's save left header */
	elem = doc.createElement("leftheader");
	elem2 = doc.createElement("values");
	text.clear();
	for (i = 0; i < fh; i++)
	{
		if (!(text.length() == 0 || text.endsWith(";")))
			text.append(';');
		if (mode != CWType::PaintCrossword)
			for (j = 0; j < ((mode == CWType::FillUpHeaders) ? (lw) : (getGroupCnt(false, i))); j++)
			{
				if (lhdr[i][j] == 0)
					continue;
				if (!(text.length() == 0 || text.endsWith(";")))
				{
					text.append(',');
				}
				text.append(QString::number(lhdr[i][j]));
			}
		else
		{
			quint16 currGroup = 0;
			for (j = 0; j < fw; j++)
			{
				CellState currState = field[j][i];
				if (currState == csFilled && j < fw - 1)
					currGroup++;
				else
				{
					if (j == fw - 1 && currState == csFilled)
						currGroup++;
					if (currGroup != 0)
					{
						if (!(text.length() == 0 || text.endsWith(";")))
							text.append(',');
						text.append(QString::number(currGroup));
						currGroup = 0;
					}
				}
			}
		}
	}

	elem2.appendChild(doc.createTextNode(text));
	elem.appendChild(elem2);
	domElem.appendChild(elem);
	
	/* записываем состояния клеток поля, если надо: */
	bool needSave = false;
	for (i = 0; i < fw; i++)
		for (j = 0; j < fh; j++)
			if (field[i][j] != csUndef)
				needSave = true;
	if (needSave && mode == CWType::SolvingCrossword)
	{
		elem = doc.createElement("field");
		for (i = 0; i < fw; i++)
			for (j = 0; j < fh; j++)
				if (field[i][j] != csUndef)
				{
					QDomElement cell = doc.createElement("cell");
					cell.setAttribute("col", i);
					cell.setAttribute("row", j);
					if (field[i][j] == csFilled)
						cell.setAttribute("state", "filled");
					else if (field[i][j] == csEmpty)
						cell.setAttribute("state", "empty");
					elem.appendChild(cell);
				}
		domElem.appendChild(elem);
	}
	
	doc.appendChild(domElem);

	QString fileName = QFileDialog::getSaveFileName(this, tr("Save crossword"), NULL,
			tr("Crosswords (*.jcw);;All files(*)"));
	if (fileName == NULL)					// если ничё не выбрано, то выход
		return;

	if (fileName.right(4) != ".jcw")
		fileName.append(".jcw");

	QFile* file = new QFile(fileName);
	if (!file->open(QIODevice::WriteOnly))
	{
		QMessageBox::critical(this, tr("Error"), tr("Can't save to file"));
		return;
	}

	QTextStream(file) << doc.toString();
	file->close();
}

void ClassicCW::updateProgress()
{
	emit cellStateChanged(quint16(-1), quint16(-1));
	updatePixmap();
}

void ClassicCW::updatePixmap()
{
	QSize fieldSize((margin * 2) + (cellSize * (lw + fw)), (margin * 2) + (cellSize * (th + fh)));
	QSize parentSize = parentWidget()->size();
	resize(fieldSize.expandedTo(parentSize));
	
	pixmap = QPixmap(size());
	pixmap.fill(this, 0, 0);
	QPainter painter(&pixmap);
	painter.initFrom(this);
	
	QPen penNormal = painter.pen();			// перо рисования границ
	penNormal.setWidth(1);
	QPen penBold = painter.pen();			// перо рисования жирных границ
	penBold.setWidth(2);
	QPen greenPen;
	greenPen.setColor(Qt::green);
	QFont font = painter.font();
	font.setPointSize(cellSize * 0.7);		// установка размера шрифта в зависимости от размера клетки
	painter.setFont(font);
	
	quint16 i;
	quint16 j;
	quint16 pointX = margin + lw * cellSize;	// координаты начальной точки поля
	quint16 pointY = margin + th * cellSize;
	painter.save();

	/* рисуем линии поля и цифры */
	if (mode != CWType::FillUpHeaders)
	{
		if (!(mode == CWType::SolvingCrossword && isSolved))
		{
			painter.setBrush(QColor(210, 210, 210));

			if (fX - lw >= 0)
				painter.drawRect(margin + fX * cellSize, margin, cellSize, cellSize * (th + fh));
			if (fY - th >= 0)
				painter.drawRect(margin, margin + fY * cellSize, cellSize * (lw + fw), cellSize);
		}
	}
	else
		if ((editHeaderX >= lw && editHeaderY < th) || (editHeaderX < lw && editHeaderY >= th))
		{
			if (isEdit)
				painter.setBrush(QColor(200, 255, 255));
			else
				painter.setBrush(QColor(Qt::yellow));
			painter.drawRect(margin + editHeaderX * cellSize, margin + editHeaderY * cellSize, cellSize, cellSize);
		}

	painter.restore();
	quint16 bottomY = (fh + th) * cellSize;	// нижняя координата Y верхнего заголовка
	quint16 rigthX = fw * cellSize;			// правая координата X верхнего заголовка
	painter.save();
	painter.translate(pointX, margin);

	for (i = 0; i <= fw; i++)				// рисуем вертикальные линии
		if (i % 5 == 0 || i == fw)			// каждую 5 границу рисуем жирнее
		{
			painter.setPen(penBold);
			if (!isSolved || i == 0)
				painter.drawLine(i * cellSize, 0, i * cellSize, bottomY);
			else
				painter.drawLine(i * cellSize, 0, i * cellSize, th * cellSize);
			painter.setPen(penNormal);
		}
		else
			if (!isSolved || i == fw || i == 0)
				painter.drawLine(i * cellSize, 0, i * cellSize, bottomY);
			else
				painter.drawLine(i * cellSize, 0, i * cellSize, th * cellSize);

	painter.setPen(penBold);
	painter.drawLine(0, 0, rigthX, 0);
	painter.setPen(penNormal);
	for (i = 1; i < th; i++)				// рисуем горизонтальные линии
		painter.drawLine(0, i * cellSize, rigthX, i * cellSize);

	for (i = 0; i < fw; i++)				//пишем цифры верхнего заголовка
		for (j = 0; j < getGroupCnt(true, i); j++)
			if (thdr[i][j] != 0)
			{
				painter.drawText(i * cellSize, (th - getGroupCnt(true, i) + j) * cellSize, cellSize, cellSize, Qt::AlignCenter, QString::number(qAbs(thdr[i][j])));
				if (thdr[i][j] < 0)
					painter.drawLine((i + 1) * cellSize, (th - getGroupCnt(true, i) + j + 1) * cellSize,
							i * cellSize, (th - getGroupCnt(true, i) + j) * cellSize); //если цифра в заголовке отрицательная - перечёркиваем её в заголовке
			}
	painter.restore();
	
	bottomY = fh * cellSize;
	rigthX = (fw + lw) * cellSize;
	painter.save();
	painter.translate(margin, pointY);

	for (i = 0; i <= fh; i++)				// рисуем горизонтальные линии
		if (i % 5 == 0 || i == fh)			// каждую 5 границу рисуем жирнее
		{
			painter.setPen(penBold);
			if (!isSolved || i == 0)
				painter.drawLine(0, i * cellSize, rigthX, i * cellSize);
			else
				painter.drawLine(0, i * cellSize, lw * cellSize, i * cellSize);
			painter.setPen(penNormal);
		}
		else
			if (!isSolved || i == fh || i == 0)
				painter.drawLine(0, i * cellSize, rigthX, i * cellSize);
			else
				painter.drawLine(0, i * cellSize, lw * cellSize, i * cellSize);


	painter.setPen(penBold);
	painter.drawLine(0, 0, 0, bottomY);
	painter.setPen(penNormal);
	for (i = 1; i < lw; i++)				// рисуем вертикальные линии
		painter.drawLine(i * cellSize, 0, i * cellSize, bottomY);

	for (i = 0; i < fh; i++)				//пишем цифры левого заголовка
		for (j = 0; j < getGroupCnt(false, i); j++)
			if (lhdr[i][j] != 0)
			{
				painter.drawText((j + lw - getGroupCnt(false, i)) * cellSize, i * cellSize, cellSize, cellSize, Qt::AlignCenter, QString::number(qAbs(lhdr[i][j])));
				if (lhdr[i][j] < 0)
					painter.drawLine((j + lw - getGroupCnt(false, i)) * cellSize, i * cellSize,
							(j + lw - getGroupCnt(false, i) + 1) * cellSize, (i + 1) * cellSize); //если цифра в заголовке отрицательная - перечёркиваем её в заголовке
			}
	painter.restore();
	
	painter.save();
	painter.translate(pointX, pointY);
	painter.setBrush(Qt::SolidPattern);
	int pointRadius = cellSize * 0.1;
	for (i = 0; i < fw; i++)
		for (j = 0; j < fh; j++)
		{
			QPen oldPen = painter.pen();
			if (mode == CWType::SolvingCrossword && (colsRes->at(i) || rowsRes->at(j)))
			{
				painter.setBrush(Qt::green);
				if (isSolved)
					painter.setPen(greenPen);
			}
			else
				painter.setBrush(Qt::black);
			if (mode != CWType::FillUpHeaders)
			{
				if (field[i][j] == csFilled)
					painter.drawRect(i * cellSize, j * cellSize, cellSize, cellSize);
				else if (field[i][j] == csEmpty && mode != CWType::PaintCrossword)
					if (!(mode == CWType::SolvingCrossword && isSolved))
					{
						painter.setBrush(Qt::black);
						painter.drawEllipse((i + 0.5) * cellSize - pointRadius, (j + 0.5) * cellSize -
						 pointRadius, pointRadius * 2, pointRadius * 2);
					}
			}

			if (mode == CWType::SolvingCrossword && (colsRes->at(i) || rowsRes->at(j)))
			{
				painter.setBrush(Qt::black);
				painter.setPen(oldPen);
			}
		}
	painter.restore();
	
	/* рисуем размеры поля */
	font.setPointSize(cellSize * 1.2);
	painter.setFont(font);
	painter.drawText(margin, margin, lw * cellSize, th * cellSize, Qt::AlignCenter,
			QString("%1x%2").arg(fw).arg(fh));

	update();
}

bool ClassicCW::solve()
{
	bool parseCols = true; //Начнём с парсинга по столбцам

	QBitArray cols(fw, true), rows(fh, true), contCols(fw), contRows(fh); //Массивы, означающие, каие строки и столбцы проверять

	quint16 curr; //Указатель на текущую строку/столбец
	QBitArray * currArr = &cols; //Указатель на массив столбцов/строк
	quint16 * maxVal = &fh; //Указатель на длину ряда
	quint16 * cntRows = &fw; //Указатель количества рядов(столбцов или строк) в кроссворде
	qint16** * currdr = &thdr;

	int i, j, k;

	qint16 **** coords = new qint16 *** [2]; //Массив, содержащий теоретические границы групп всего кроссворда
	coords[0] = new qint16 ** [fw]; //Тут будут столбцы (группы верхнего заголовка)
	coords[1] = new qint16 ** [fh]; //Тут будут строки (группы левого заголовка)
	for (i = 0; i < fw; i++)
	{
		coords[0][i] = new qint16 * [th]; //Резервирование мест под максимальное число групп верхнего заголовка
		for (j = 0; j < th; j++)
		{
			coords[0][i][j] = new qint16 [2]; //Резервирование места для значений начала и конца границ групп
			coords[0][i][j][0] = -1; //Начальные значения инициализируем за верхним краем кроссворда
			coords[0][i][j][1] = fh; //Конечные - нижним
		}
	}
	for (i = 0; i < fh; i++)
	{
		coords[1][i] = new qint16 * [lw]; //Резервирование мест под максимальное число групп левого заголовка
		for (j = 0; j < lw; j++)
		{
			coords[1][i][j] = new qint16 [2]; //Резервирование места для значений начала и конца границ групп
			coords[1][i][j][0] = -1; //Начальные значения инициализируем за левым краем кроссворда
			coords[1][i][j][1] = fw; //Конечные - за правым
		}
	}

	//coords[столбцы(0)/строки(1)][номер][группа][начальная граница(0)/конечная граница(1)]

	qint16 grp, grp2;
	CellState currState;
	qint16 bord;
	qint16 pnt = 0; //Указатель в массиве на строки или столбцы

	bool emp, filledExists, notToSetBit;
	int summ, max, min, minClosest, summF, currRF, currRU;
	for (curr = 0; (cols != contCols || rows != contRows) && !getSolved(); curr++) //Цикл до тех пор, пока есть необходимость проверять столбцы или строки или до того, пока кроссворд не будет решён
	{
		notToSetBit = false;
		if (curr > (*cntRows) - 1)
		{
			curr = 0;
			if (parseCols)
			{
				currArr = &rows;
				maxVal = &fw;
				cntRows = &fh;
				currdr = &lhdr;
			}
			else
			{
				currArr = &cols;
				maxVal = &fh;
				cntRows = &fw;
				currdr = &thdr;
			}
			parseCols = !parseCols;
			pnt = 1 - pnt;
		}
		if (!currArr->at(curr))
			continue;

		if ((parseCols && colsRes->at(curr)) || (!parseCols && rowsRes->at(curr)))
		{
			for (i = 0; i < (*maxVal); i++)
			{
				currState = (parseCols) ? field[curr][i] : field[i][curr];
				if (currState == csUndef)
					setField(curr, i, csEmpty, parseCols, &cols, &rows, &notToSetBit);
			}
			currArr->setBit(curr, false);
			continue;
		}

//		qDebug() << "Group count in the header:" << getGroupCnt(parseCols, curr) << ((parseCols) ? "(cols)" : "(rows)") << "curr =" << curr;
		bord = -1; //Переменная, обозначающая координату края или предыдущего крестика
		for (i = 0; i < getGroupCnt(parseCols, curr); i++) //Пройдёмся циклом по группам данного ряда
		{
			grp = qAbs((*currdr)[curr][i]);
//			qDebug() << "Group:" << grp; //число группы

			//Найдём теоретическое начало группы
			if (i == 0) //Если группа первая
			{
				if (coords[pnt][curr][i][0] < 0) //Если текущее значение инициализировано, но не вычисленно
					coords[pnt][curr][i][0] = 0; //то началом будем сам край ряда
			}
			else
				if (coords[pnt][curr][i][0] < coords[pnt][curr][i - 1][0] + qAbs((*currdr)[curr][i - 1]) + 1)	//Если теоретическое начало не оказалось далее по ряду
					coords[pnt][curr][i][0] = coords[pnt][curr][i - 1][0] + qAbs((*currdr)[curr][i - 1]) + 1; //оно равно координате ячейки начала предыдущей группы + число группы + 1 пустая клетка + 1
			filledExists = false;

			for (j = coords[pnt][curr][i][0] - 1; j > -1; j--) //Необходимо определить, де был предыдущий крестик (вычислить bord)
			{
				currState = (parseCols) ? field[curr][j] : field[j][curr];
				if (currState == csEmpty)
				{
					bord = j;
					break;
				}
			}

			for (j = coords[pnt][curr][i][0]; j < grp + coords[pnt][curr][i][0]; j++) //пройдёмся теперь по каждой ячейке ряда, чтобы посмотреть, нет ли на пути группы крестиков
			{
				if (j >= (*maxVal)) //TODO: а разве не (j + grp - 1) >= (*maxVal)?
				{
					qDebug() << "Error 1";
					return false; //Не может быть такого, чтобы группа никуда не помещалась. Ошибка значит
				}

				currState = (parseCols) ? field[curr][j] : field[j][curr];
				if (currState == csEmpty) //Если встретился на пути крестик
				{
					//Проверим, вместятся ли предыдущие группы в пространство, которое счас мы анализируем
					emp = true;
					for (k = 0; k < i; k++)
					{
						if ((j - bord - 1) >= (*currdr)[curr][k])
						{
							emp = false;
							break;
						}
					}
					if (emp) // если нет, то закрашиваем его крестиками
					{
						if (filledExists) //Если в данном пространстве есть закрашенные клетки
						{
							qDebug() << "Error 2";
							return false; //значит, де-то мы накосячили
						}

						for (k = bord + 1; k < j; k++)
							setField(curr, k, csEmpty, parseCols, &cols, &rows, &notToSetBit);
					}

					coords[pnt][curr][i][0] = j + 1;
					j = coords[pnt][curr][i][0] - 1;

					filledExists = false;
				}
				else if (currState == csFilled)
					filledExists = true;
			}

			if (coords[pnt][curr][i][0] > 0)
				for (j = coords[pnt][curr][i][0] - 1; j < (*maxVal); j++) //Пройдёмся по ячейкам на предмет наличия закрашенных ячеек впритык перед теоретическим началом группы
				{
					currState = (parseCols) ? field[curr][j] : field[j][curr];
					if (currState == csFilled) //Если перед теоретическим началом группы встретилась закрашенная ячейка
						coords[pnt][curr][i][0]++; //смещаем теоретическое начало на одну ячейку
					else
						break;

					if (j >= (*maxVal) - 1 - grp) //Если закрашенная ячейка располагается так, что при смещении от неё группы последняя просто не вместится, значит, де-то мы ошиблись
					{
						qDebug() << "Error 3";
						return false;
					}
				}

			for (j = coords[pnt][curr][i][0] + grp; j < (*maxVal) ; j++) //пройдёмся по ячейкам за концом группы
			{
				currState = (parseCols) ? field[curr][j] : field[j][curr];
				if (currState == csFilled) //Если ячейки за группой закрашены
					coords[pnt][curr][i][0]++; //смещаем теоретическое начало группы на одну ячейку
				else
					break;
			}

//			qDebug() << "coords [" << i << "] [ 0 ] =" << coords[pnt][curr][i][0];
		}

		bord = (*maxVal); //Переменная, обозначающая координату края или предыдущего крестика
		for (i = getGroupCnt(parseCols, curr) - 1; i >= 0 ; i--) //Пройдёмся циклом по группам данного ряда с конца
		{
			grp = qAbs((*currdr)[curr][i]);
//			qDebug() << "Group:" << grp; //число группы

			//Найдём теоретический конец группы
			if (i == getGroupCnt(parseCols, curr) - 1) //Если группа первая
			{
				if (coords[pnt][curr][i][1] > (*maxVal) - 1) //Если текущее значение инициализировано, но не вычисленно
					coords[pnt][curr][i][1] = (*maxVal) - 1; //то началом будем сам край ряда
			}
			else
				if (coords[pnt][curr][i][1] > coords[pnt][curr][i + 1][1] - qAbs((*currdr)[curr][i + 1]) - 1)	//Если теоретический конец не оказался далее по ряду
					coords[pnt][curr][i][1] = coords[pnt][curr][i + 1][1] - qAbs((*currdr)[curr][i + 1]) - 1; //он равен координате ячейки конца предыдущей группы - число группы - 1 пустая клетка - 1

			filledExists = false;

			for (j = coords[pnt][curr][i][1] + 1; j < (*maxVal); j++) //Необходимо определить, де был предыдущий крестик (вычислить bord)
			{
				currState = (parseCols) ? field[curr][j] : field[j][curr];
				if (currState == csEmpty)
				{
					bord = j;
					break;
				}
			}

			for (j = coords[pnt][curr][i][1]; j > coords[pnt][curr][i][1] - grp; j--) //пройдёмся теперь по каждой ячейке ряда, чтобы посмотреть, нет ли на пути группы крестиков
			{
				if (j < 0)
				{
					qDebug() << "Error 4";
					return false; //Не может быть такого, чтобы группа никуда не помещалась. Ошибка значит
				}

				currState = (parseCols) ? field[curr][j] : field[j][curr];
				if (currState == csEmpty) //Если встретился на пути крестик
				{
					//Проверим, вместятся ли предыдущие группы в пространство, которое счас мы анализируем
					emp = true;
					for (k = getGroupCnt(parseCols, curr) - 1; k > i; k--)
					{
						if (bord - j - 1 == 0)
						{
							emp = false;
							break;
						}
						if ((bord - j - 1) >= (*currdr)[curr][k])
						{
							emp = false;
							break;
						}
					}
					if (emp) // если нет, то закрашиваем его крестиками
					{
						if (filledExists) //Если в данном пространстве есть закрашенные клетки
						{
							qDebug() << "Error 5";
							return false; //значит, де-то мы накосячили
						}

						for (k = bord - 1; k > j; k--)
							setField(curr, k, csEmpty, parseCols, &cols, &rows, &notToSetBit);
					}

					coords[pnt][curr][i][1] = j - 1;
					j = coords[pnt][curr][i][1] + 1;

					filledExists = false;
				}
				else if (currState == csFilled)
					filledExists = true;
			}

			if (coords[pnt][curr][i][1] < (*maxVal) - 1)
				for (j = coords[pnt][curr][i][1] + 1; j > 0; j--) //Пройдёмся по ячейкам на предмет наличия закрашенных ячеек впритык за теоретическим концом группы
				{
					currState = (parseCols) ? field[curr][j] : field[j][curr];
					if (currState == csFilled) //Если за теоретическим концом группы встретилась закрашенная ячейка
						coords[pnt][curr][i][1]--; //смещаем теоретический конец на одну ячейку
					else
						break;

					if (j <= grp) //Если закрашенная ячейка располагается так, что при смещении от неё группы последняя просто не вместится, значит, де-то мы ошиблись
					//TODO: Здесь можно сразу проверить расстояние не от начала поля, а от теоретического начала группы. Что-то вроде if (j - coords[pnt][curr][i][0] <= grp). Как вариант, правда
					{
						qDebug() << "Error 6";
						return false;
					}
				}

			for (j = coords[pnt][curr][i][1] - grp; j >= 0; j--) //пройдёмся по ячейкам за концом группы
			{
				currState = (parseCols) ? field[curr][j] : field[j][curr];
				if (currState == csFilled) //Если ячейки за группой закрашены, смещаем теоретический конец группы
					coords[pnt][curr][i][1]--;
				else
					break;
			}

			if (coords[pnt][curr][i][1] - coords[pnt][curr][i][0] + 1 < grp) //если расстояние между теоретическими началом и концом группы меньше самой группы
			{
				qDebug() << "Error 7";
				return false; //Понятно, что ето ошибка
			}

//			qDebug() << "coords [" << i << "] [ 1 ] =" << coords[pnt][curr][i][1];
		}

		for (i = 0; i < getGroupCnt(parseCols, curr); i++) //пройдёмся теперь по группам с учётом теоретических границ групп
		{
			grp = qAbs((*currdr)[curr][i]);
			for (j = coords[pnt][curr][i][1] - grp + 1; j < coords[pnt][curr][i][0] + grp; j++) //Закрашиваем клеки внутри теоретических границ
				setField(curr, j, csFilled, parseCols, &cols, &rows, &notToSetBit);
			if (coords[pnt][curr][i][0] - coords[pnt][curr][i][1] + 2 * grp - 1 == grp) //Если группа закрашивается вся
			{
				if (coords[pnt][curr][i][0] > 0) //если группа не упирается в край ряда
					setField(curr, coords[pnt][curr][i][0] - 1, csEmpty, parseCols, &cols, &rows, &notToSetBit); //ставим крестик до группы
				if (coords[pnt][curr][i][1] < (*maxVal) - 1) //если группа не упирается в другой край ряда
					setField(curr, coords[pnt][curr][i][1] + 1, csEmpty, parseCols, &cols, &rows, &notToSetBit); //ставим крестик после группы
			}

			quint16 beg;
			if (i == 0)
				beg = 0;
			else
				if (coords[pnt][curr][i - 1][1] < coords[pnt][curr][i][0])
					beg = coords[pnt][curr][i][0];
				else
					beg = coords[pnt][curr][i - 1][1] + 1;
			for (j = beg; j <= coords[pnt][curr][i][1]; j++)
			{
				currState = (parseCols) ? field[curr][j] : field[j][curr];
				if (currState == csFilled && coords[pnt][curr][i][1] > j + grp - 1)
				{
					coords[pnt][curr][i][1] = j + grp - 1;
					notToSetBit = true;
					break;
				}
			}

			if (i == getGroupCnt(parseCols, curr) - 1)
				beg = (*maxVal) - 1;
			else
				if (coords[pnt][curr][i + 1][0] > coords[pnt][curr][i][1])
					beg = coords[pnt][curr][i][1];
				else
					beg = coords[pnt][curr][i + 1][0] - 1;
			for (j = beg; j >= coords[pnt][curr][i][0]; j--)
			{
				currState = (parseCols) ? field[curr][j] : field[j][curr];
				if (currState == csFilled && coords[pnt][curr][i][0] < j - grp + 1)
				{
					coords[pnt][curr][i][0] = j - grp + 1;
					notToSetBit = true;
					break;
				}
			}
		}

		currRF = 0;
		for (i = 0; i < (*maxVal); i++) //Пройдёмся по клеткам
		{
			currState = (parseCols) ? field[curr][i] : field[i][curr];

			if (currState == csFilled)
				currRF++;

			if (currState == csUndef || currState == csEmpty)
			{
				if (currRF > 0)
				{
					minClosest = 0;
					for (j = 0; j < getGroupCnt(parseCols, curr); j++) //Найдём ближайшую минимальную группу к закрашенной области
					{
						grp = qAbs((*currdr)[curr][j]);
						if (coords[pnt][curr][j][0] <= i - currRF && coords[pnt][curr][j][1] >= i - 1) //если группе принадлежит данная ячейка
							if (grp >= currRF)
							{
								if (minClosest == 0)
									minClosest = grp;
								else
									if (grp < minClosest)
										minClosest = grp;
							}
					}

					for (j = 1; j <= minClosest - currRF; j++) //найдём в левой (верхней) стороне крестики или борты и увеличим закрашенную область с учётом етих препятствий
					{
						if (i - currRF - j < 0) //если упёрлись в борт
						{
							for (k = currRF; k <= minClosest - j; k++)
							{
								if (i + k - currRF > (*maxVal) - 1)
								{
									qDebug() << "Error 8";
									return false; //Минимальная группа для данной области каким-то макаром не помещается на поле в принципе
								}
								setField(curr, i + k - currRF, csFilled, parseCols, &cols, &rows, &notToSetBit);
							}
						}
						else
						{
							currState = (parseCols) ? field[curr][i - currRF - j] : field[i - currRF - j][curr];
							if (currState == csEmpty)
							{
								for (k = currRF; k <= minClosest - j; k++)
								{
									if (i + k - currRF > (*maxVal) - 1)
									{
										qDebug() << "Error 9";
										return false; //Минимальная группа для данной области не помещается в нашу область
									}
									setField(curr, i + k - currRF, csFilled, parseCols, &cols, &rows, &notToSetBit);
								}
							}
						}
					}

					for (j = 1; j <= minClosest - currRF; j++) //найдём в правой стороне крестики или борты и увеличим закрашенную область с учётом етих препятствий
					{
						if (i + j - 2 >= (*maxVal) - 1) //если упёрлись в борт
						{
							for (k = currRF; k <= minClosest - j; k++)
							{
								if (i - k - 1 < 0)
								{
									qDebug() << "Error 8";
									return false; //Минимальная группа для данной области каким-то макаром не помещается на поле в принципе
								}
								setField(curr, i - k - 1, csFilled, parseCols, &cols, &rows, &notToSetBit);

							}
						}
						else
						{
							currState = (parseCols) ? field[curr][i + j - 1] : field[i + j - 1][curr];
							if (currState == csEmpty)
							{
								for (k = currRF; k <= minClosest - j; k++)
								{
									if (i - k - 1 < 0)
									{
										qDebug() << "Error 9";
										return false; //Минимальная группа для данной области не помещается в нашу область
									}
									setField(curr, i - k - 1, csFilled, parseCols, &cols, &rows, &notToSetBit);
								}
							}
						}
					}

					while (i < (*maxVal) - 1)
					{
						currState = (parseCols) ? field[curr][i] : field[i][curr];
						if (currState == csFilled)
							i++;
						else
							break;
					}
					currRF = 0;
				}
			}
		}

		summ = min = 0;
		for (i = 0; i < getGroupCnt(parseCols, curr); i++) //Подсчитаем сумму групп и узнаем минимальную из них
		{
			grp = qAbs((*currdr)[curr][i]);
			summ += grp;
			if (i == 0)
				min = grp;
			else
				if (min > grp)
					min = grp;
		}

		summF = currRF = currRU = max = 0;
		for (i = 0; i < (*maxVal); i++) //Подсчитаем, сколько закрасили клеток в ряду и по возможности ограничим крестиками такие группы, которые равны максимальной, и проставим крестиками пространства, длина которых меньше минимальной группы
		{
			currState = (parseCols) ? field[curr][i] : field[i][curr];
			if (currState == csUndef || currState == csFilled)
				currRU++;
			if (currState == csFilled)
			{
				for (j = 0; j < getGroupCnt(parseCols, curr); j++) //Подсчитаем максимальную группу для данной ячейки
				{
					if (coords[pnt][curr][j][0] <= i && coords[pnt][curr][j][1] >= i) //если группе принадлежит данная ячейка
					{
						grp = qAbs((*currdr)[curr][j]);
						if (grp > max) //сравним группу уже с имеющимся максимумом
							max = grp;
					}
				}
				summF++;
				currRF++;
				if (i == (*maxVal) - 1)
				{
					if (currRF == max && max != 0) //сравним длину закрашенной области с максимальной группой для данной области
					{
						if (i - max > 0)
							setField(curr, i - max, csEmpty, parseCols, &cols, &rows, &notToSetBit);
					}
					currRF = max = 0;
				}
			}
			if (currState == csUndef || currState == csEmpty)
			{
				if (currRF == max && max != 0) //сравним длину закрашенной области с максимальной группой для данной области
				{
					if (i - max - 1 > 0)
						setField(curr, i - max - 1, csEmpty, parseCols, &cols, &rows, &notToSetBit);
					if (i < (*maxVal) - 1)
						setField(curr, i, csEmpty, parseCols, &cols, &rows, &notToSetBit);
				}
				currRF = max = 0;
			}
			if (currState == csEmpty || i == (*maxVal) - 1)
			{
				if (currRU < min)
				for (j = i - currRU; (i == (*maxVal) - 1) ? (j <= i) : (j < i); j++)
				{
					currState = (parseCols) ? field[curr][j] : field[j][curr];
					if (currState == csFilled)
					{
						qDebug() << "Error 10";
						return false; //В свободном пространстве, длина которого меньше минимальной группы ряда есть закрашенные ячейки - ошибка
					}
					setField(curr, j, csEmpty, parseCols, &cols, &rows, &notToSetBit);
				}
				currRU = 0;
			}
		}

		if (summ == summF) //Если в ряду все группы закрашены
			for (i = 0; i < (*maxVal); i++)
			{
				currState = (parseCols) ? field[curr][i] : field[i][curr];
				if (currState == csUndef)
					setField(curr, i, csEmpty, parseCols, &cols, &rows, &notToSetBit); //проставляем крестики в пустых ячейках
			}

		if (summ < summF)
		{
			qDebug() << "Error 11";
			return false; //Число закрашенных ячеек превышает сумму групп - ошибка же
		}

		min = -1; //В данном случае переменная будет содержать в себе координату начала
		for (i = 0; i < (*maxVal); i++) //проверим каждую пустую и закрашенную ячейку на принадлежность к каким-либо группам
		{
			summ = 0; //Сумма групп, к которой принадлежит данная клетка
			currState = (parseCols) ? field[curr][i] : field[i][curr];
			if (currState == csUndef)
			{ //Проверяем на принадлежность к группам
				for (j = 0; j < getGroupCnt(parseCols, curr); j++) //Ищем количество групп, которым принадлежит данная ячейка
				{
					if (coords[pnt][curr][j][0] <= i && coords[pnt][curr][j][1] >= i)
					{
						grp = qAbs((*currdr)[curr][j]); //Узнаем числовое значение группы
						summ++;
						k = j; //Занесём в переменную местоположение группы в ряду
					}
				}
			}

			if (currState == csFilled)
			{
				if (min == -1) //Если данная ячейка - первая закрашенная на пути (то есть после пустой или крестика или первая в ряду)
					min = i; //Задаём ей начало
			}

			if (currState == csUndef && summ == 0) //Если данная ячейка пустая и не принадлежит ни одной из групп
				setField(curr, i, csEmpty, parseCols, &cols, &rows, &notToSetBit); //Ставим в ней крестик

			if (i + 1 < (*maxVal))
				currState = (parseCols) ? field[curr][i + 1] : field[i + 1][curr]; //Проверим следующую за текущей ячейку

			if (i + 1 == (*maxVal) || currState == csUndef || currState == csEmpty) //Если данная ячейка не закрашена или там крестик или там вобще конец ряда
			{
				if (min > -1) //Если мы нашли группу закрашенных клеток в предыдущих шагах цикла
				{
					summ = 0;
					for (j = 0; j < getGroupCnt(parseCols, curr); j++) //Ищем количество групп, которым принадлежит данный закрашенный диапазон
					{
						grp = qAbs((*currdr)[curr][j]); //Узнаем числовое значение группы
						if (coords[pnt][curr][j][0] <= i && coords[pnt][curr][j][1] >= i && i - min + 1 <= grp) //Если данный диапазон принадлежит текущей группе и равен либо меньше по размеру группы
						{
							summ++;
							k = j; //Занесём в переменную местоположение группы в ряду
							grp2 = grp; //А также запомним её числовое значение
						}
					}
					if (summ == 1) //Если нашлась только одна группа на данный диапазон
					{
						if (min - coords[pnt][curr][k][0] > grp2 - 1){ //если теоретическое начало группы отлежит от данной закрашенной области более чем на длину группы, то перемещаем его на расстояние от начала закрашенной области равное длине группы
							coords[pnt][curr][k][0] = i - grp2 + 1;
							notToSetBit = true; //Обязательно пройдёмся по данному ряду в следующем цикле
						}
						if (coords[pnt][curr][k][1] - i > grp2 - 1){ //если теоретический конец группы отлежит от данной закрашенной области более чем на длину группы, то перемещаем его на расстояние от конца закрашенной области равное длине группы
							coords[pnt][curr][k][1] = i + grp2 - 1;
							notToSetBit = true; //Обязательно пройдёмся по данному ряду в следующем цикле
						}
					}
				}
				min = -1; //Сбросим начало закрашенной группы
			}
		}

		if (!notToSetBit) currArr->setBit(curr, false);
	}
	qDebug() << "Ready";
	return true;
}

void ClassicCW::setField(quint16 x, quint16 y, CellState st, bool isCol, QBitArray * c, QBitArray * r, bool * notToSetBit)
{
	CellState currState = (isCol) ? field[x][y] : field[y][x];
	if (currState != st)
	{
		if (isCol)
		{
			fX = x + lw;
			fY = y + th;
			r->setBit(y, true);
		}
		else
		{
			fX = y + lw;
			fY = x + th;
			c->setBit(y, true);
		}
//		CellState newState = st;
		changeCellState(st/*newState*/);
		(*notToSetBit) = true;
		updatePixmap();
	}
}

void ClassicCW::undo()
{
	if (undoList.count() == 0 || isSolved)
		return;

	History undoStep = undoList.takeLast();
	History redoStep = undoStep;
	redoStep.state = field[undoStep.coordX][undoStep.coordY];
	field[undoStep.coordX][undoStep.coordY] = undoStep.state;
	redoList.append(redoStep);
	updatePixmap();
}

void ClassicCW::redo()
{
	if (redoList.count() == 0 || isSolved)
		return;

	History redoStep = redoList.takeLast();
	History undoStep = redoStep;
	undoStep.state = field[redoStep.coordX][redoStep.coordY];
	field[redoStep.coordX][redoStep.coordY] = redoStep.state;
	undoList.append(undoStep);
	updatePixmap();
}

void ClassicCW::solved()
{
	isSolved = true;
	undoList.clear();
	redoList.clear();
}
